﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows.Forms;
using FileLink.Properties;
using VideoLink.Links;
using VideoLink.Utility;
using FileLink.FilesInfo;
using System.Collections.Generic;

namespace FileLink
{
    /// <summary>
    /// Главная форма приложения.
    /// </summary>
    public partial class FileLinkForm : Form
    {
        #region Private fields

        /// <summary>
        /// Показывает, что создание подключения было обработано
        /// Устанавливается в True, когда подключение разорвано
        /// и в False при обнаружении подключения
        /// </summary>
        private bool connectionHandled = false;

        /// <summary>
        /// Соединение.
        /// </summary>
        private ILink link;

        /// <summary>
        /// Счётчик статистики.
        /// </summary>
        private Statistics stat;

        /// <summary>
        /// Сюда складываются полученные и получаемые файлы
        /// </summary>
        private string receivedFilesDir;

        /// <summary>
        /// Передаваемый в настоящее время файл.
        /// Первый из SendFiles, передача которого ещё не закончена.
        /// </summary>
        private FileView currentSendFile;

        /// <summary>
        /// Принимаемый в настоящее время файл.
        /// Первый из ReceiveFiles, приём которого не закончен.
        /// </summary>
        private FileView currentReceiveFile;

        /// <summary>
        /// Список описний файлов, которые либо были переданы, либо передаются,
        /// либо ожидают передачи
        /// </summary>
        private List<FileView> SendFiles
        {
            get
            {
                return sendDGV.DataSource as List<FileView>;
            }
        }

        /// <summary>
        /// Список описний файлов, которые либо были приняты, либо принимаются
        /// </summary>
        private List<FileView> ReceiveFiles
        {
            get
            {
                return receiveDGV.DataSource as List<FileView>;
            }
        }

        /// <summary>
        /// Буфер, в который читаются из sendStream фрагменты
        /// </summary>
        private byte[] sendBuffer;

        /// <summary>
        /// Поток, из которого берутся данные для передачи.
        /// </summary>
        private FileStream sendStream;

        /// <summary>
        /// Поток, в который записываются полученные фрагменты.
        /// </summary>
        private FileStream receiveStream;

        #endregion

        /// <summary>
        /// Создать новый экземпляр класса по умолчанию.
        /// </summary>
        public FileLinkForm()
        {
            Settings.Default.Reload();

            InitializeComponent();

            receivedFilesDir = Application.StartupPath
                + Path.DirectorySeparatorChar
                + Settings.Default.ReceivedFilesFolder
                + Path.DirectorySeparatorChar;

            stat = new Statistics(statisticsPG);
            DebugLog.Initialize(debugTB);

            InitializeGrids();
            LoadCustomSettings();
        }

        #region Link

        /// <summary>
        /// Установить подключение
        /// </summary>
        private void InitializeLink()
        {
            link = LinkLoader.GetLink(Settings.Default.OtherSideIP,
                Path.GetFullPath(Settings.Default.SmartLinkAssemblyName),
                Settings.Default.SmartLinkClassName);

            var res = link.OpenDevice();
            if (res != 0)
                Utils.ShowError("OpenDevice returned: " + res.ToString(),
                    "Device open error!");

            link.NewFileReceived += new NewFileHandler(link_NewFileReceived);
            link.GetStatus += new TransieveStatusHandler(link_GetStatus);
            link.SendStatus += new TransieveStatusHandler(link_SendStatus);
        }

        /// <summary>
        /// Вызывается при изменении состояния процесса передачи файла.
        /// Пока что не делается ничего.
        /// </summary>
        /// <param name="finished">
        /// 1 - если передача завершена, 0 в противном случае.
        /// </param>
        /// <param name="progress">
        /// Процент переданных данных от всего объёма файла.
        /// </param>
        private void link_SendStatus(int finished, int progress)
        {
            //если фрагмент передан
            if (finished == 1)
            {
                BeginInvoke(new Action(() =>
                    {
                        Application.DoEvents();

                        SendNextFragment();
                    }));
            }
        }

        /// <summary>
        /// Вызывается при изменении состояния процесса получения файла.
        /// Пока что не делается ничего.
        /// </summary>
        /// <param name="finished">
        /// 1 - если получение завершено, 0 в противном случае.
        /// </param>
        /// <param name="progress">
        /// Процент полученных данных от всего объёма файла.
        /// </param>
        private void link_GetStatus(int finished, int progress)
        {
        }

        /// <summary>
        /// Вызывается при получении нового файла.
        /// На момент вызова события файл уже готов к использованию.
        /// </summary>
        /// <param name="fileName">
        /// Имя полученного файла.
        /// </param>
        private void link_NewFileReceived(string fileName)
        {
            BeginInvoke(new Action(() =>
                {
                    try
                    {
                        HandleFragmentReception(fileName);
                    }
                    catch (SerializationException)
                    {
                        DebugLog.Instance.WriteLine("SerializationException occured during " +
                            "loading received file");
                    }
                }));
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Загрузить и применить настройки приложения
        /// </summary>
        private void LoadCustomSettings()
        {
            logMI.Checked = Settings.Default.ShowLog;
            statMI.Checked = Settings.Default.ShowStatistics;
        }

        /// <summary>
        /// Сохранить изменения в настройках приложения
        /// </summary>
        private void SaveCustomSettings()
        {
            Settings.Default.ShowLog = logMI.Checked;
            Settings.Default.ShowStatistics = statMI.Checked;
            Settings.Default.Save();
        }

        /// <summary>
        /// Инициализировать таблицы для отображения
        /// </summary>
        private void InitializeGrids()
        {
            sendDGV.DataSource = new List<FileView>();

            receiveDGV.DataSource = new List<FileView>();
        }

        #endregion

        #region Data manipulation

        /// <summary>
        /// Обработать получение заголовка
        /// </summary>
        /// <param name="name">
        /// Имя временного файла, в котором содержится описание принимаемого файла
        /// </param>
        private void HeaderReceived(string name)
        {
            var header = (FileHeader)Utils.Deserialize(name);
            
            //готовим место для сохранения файла
            if (!Directory.Exists(receivedFilesDir))
                Directory.CreateDirectory(receivedFilesDir);
            header.FullName = receivedFilesDir + Path.GetFileName(header.FullName);

            receiveStream = new FileStream(header.FullName,
                FileMode.Create);

            currentReceiveFile = new FileView(header);

            ReceiveFiles.Add(currentReceiveFile);
        }

        /// <summary>
        /// Обработать получение очередной порции данных
        /// </summary>
        /// <param name="name">
        /// Имя временного файла, в котором содержатся полученные данные
        /// </param>
        private void HandleFragmentReception(string name)
        { 
            //если ничего до этого не принималось
            if (currentReceiveFile == null)
                HeaderReceived(name);
            else //если что-то уже принимается
            {
                var frag = (FileFragment)Utils.Deserialize(name);
                //дописываем в файл полученные данные
                receiveStream.Write(frag.Data,
                    0,
                    frag.Data.Length);

                currentReceiveFile.FragmentsTransmitted++;

                //если все 100% получено
                if (currentReceiveFile.TransmitProgress == 100)
                    FinishReception();
            }

            //статистика и удаление временного файла
            stat.BytesReceived(Utils.GetFileSize(name));
            File.Delete(name);

            receiveDGV.RefreshList<FileView>();
        }

        /// <summary>
        /// Завершить получение текущего файла.
        /// </summary>
        private void FinishReception()
        {
            currentReceiveFile = null;
            receiveStream.Close();
            receiveStream = null;
            GC.Collect();
        }

        /// <summary>
        /// Начать передачу следующего файла.
        /// </summary>
        /// <returns>
        /// True - если новая передача начата,
        /// False -  если нечего было передавать
        /// </returns>
        private bool StartNewTransmission()
        {
            currentSendFile = SendFiles.FirstOrDefault((file) =>
                file.TransmitProgress != 100);

            if (currentSendFile == null)
                return false;

            sendBuffer = new byte[FileFragment.MaxFragmentLength];
            sendStream = new FileStream(currentSendFile.Info.FullName,
                FileMode.Open);

            SendObject(currentSendFile.Info);

            return true;
        }

        /// <summary>
        /// Послать следующий фрагмент текущего файла.
        /// </summary>
        private void SendNextFragment()
        {
            //если не подключены
            if (!link.Active)
                return;

            //если передаём первый файл в очереди
            if (currentSendFile == null)
                if (!StartNewTransmission())
                    return;//если все уже переданы

            //читаем фрагмент из файла
            var bytesRead = sendStream.Read(sendBuffer,
                0,
                sendBuffer.Length);

            var frag = new FileFragment(currentSendFile.Info.ID,
                currentSendFile.FragmentsTransmitted,
                sendBuffer);
            
            SendObject(frag);

            currentSendFile.FragmentsTransmitted++;

            sendDGV.RefreshList<FileView>();

            //если все фрагменты этого файла уже переданы
            if (currentSendFile.Info.FragmentsTransmitted ==
                currentSendFile.Info.FragmentsCount)
            {
                FinishTransmission();
                stat.BytesSent(0);
            }
        }

        /// <summary>
        /// Завершить передачу текущего файла,
        /// освободить все ресурсы.
        /// </summary>
        private void FinishTransmission()
        {
            currentSendFile = null;
            sendBuffer = null;
            sendStream.Close();
            sendStream = null;
            GC.Collect();
        }

        /// <summary>
        /// Передать объект
        /// </summary>
        /// <param name="obj">Объект для передачи</param>
        private void SendObject(object obj)
        {
            //сохраняем во временный файл для передачи
            var tmpFile = Path.GetTempFileName();
            var bytesSent = Utils.Serialize(tmpFile, obj);

            //учитываем статистику
            stat.BytesSent(bytesSent);

            //передаём
            link.SendFile(tmpFile);
            File.Delete(tmpFile);
        }

        #endregion

        #region Form events' handlers

        private void updateTimer_Tick(object sender, EventArgs e)
        {
            if (!link.Active)
            {
                connectionStateSL.Text = "Не подключено";
                connectionHandled = false;
                return;
            }

            if (!connectionHandled)
            {
                connectionStateSL.Text = "Подключено";
                //если передача ещё не идёт
                if (currentSendFile == null)
                    SendNextFragment();
                connectionHandled = true;
            }
        }

        private void VideoLinkForm_Load(object sender, EventArgs e)
        {
            DebugLog.Instance.WriteWithTime("Started!");

            InitializeLink();
        }

        private void VideoLinkForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            link.CloseDevice();

            SaveCustomSettings();
        }

        private void statMI_CheckedChanged(object sender, EventArgs e)
        {
            vertSC.Panel2Collapsed = !statMI.Checked;
        }

        private void debugMI_CheckedChanged(object sender, EventArgs e)
        {
            mainSC.Panel2Collapsed = !logMI.Checked;
        }

        private void addFilesMI_Click(object sender, EventArgs e)
        {
            addFilesDialog.ShowDialog();
        }

        private void addFilesDialog_FileOk(object sender, CancelEventArgs e)
        {
            foreach (var fileName in addFilesDialog.FileNames)
                SendFiles.Add(new FileView(new FileHeader(fileName)));

            //если передача ещё не идёт
            if (currentSendFile == null)
                SendNextFragment();
        }

        private void dgv_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            var path = ((sender as DataGridView).DataSource as List<FileView>)[e.RowIndex]
                .Info.FullName;
            Process.Start(path);
        }

        #endregion
    }
}
